﻿using System;
using System.Net;
using System.Windows;
using System.Net.Sockets;
using System.Collections.Generic;
namespace Fetion.Core
{
    public class TcpChannel:IDisposable
    {
        private static Dictionary<int, object> dicWait = new Dictionary<int, object>();
        public static Request<T> GetRequestBySeqId<T>(int id)
        {
            if (dicWait.ContainsKey(id))
            {
                Request<T> r = (Request<T>)dicWait[id];
                dicWait.Remove(id);
                return r;
            }
            else
            {
                return null;
            }
        }
        public static void AddRequest(int id,object model)
        {
            dicWait.Add(id,model);
        }
        private static int receiveBufferSize=64*1024;
        private static int sendBufferSize = 64*1024;
        private SocketAsyncEventArgs mSendEventArgs;
        private SocketAsyncEventArgs mReceiveEventArgs;
        private SocketError mSocketError;  
        public SocketError SocketError
        {
            get
            {
                return mSocketError;
            }
        }
        private Socket mSocket;
        private Package mPackage;
        public Package Package
        {
            get
            {
                return mPackage;
            }
        }
        public Socket Socket
        {
            get
            {
                return mSocket;
            }
        }
        private bool mDisconnected = true;
        public double Delay
        {
            get;
            set;
        }
        private DateTime mBeginConnectTime;
        public bool Disconnected
        {
            get
            {
                return mDisconnected;
            }
        }
        private string mFailedInfo;
        public string FailedInfo
        {
            get
            {
                return mFailedInfo;
            }
        }
        public event EventChannelReceive Receive;
        public event EventChannelError Error;
        public event EventChannelConnected Connected;
        public event EventChannelDisposed Disposed;
        public event EventSendCompleted Completed;
        private void Close()
        {
            try
            {
                Socket.Shutdown(SocketShutdown.Both);

            }
            catch
            {
            }
            try
            {
                Socket.Close();

            }
            catch
            {
            }
        }
        public TcpChannel(Package package)
        {
           
            mPackage = package;
            mPackage.Channel = this;
            Package.Receive += OnMessageReceive;
        
        }
        public void CallError(Exception e)
        {
            OnError(new EventChannelErrorArgs { Channel= this, Error=e });
        }
        private void OnConnected(EventChannelArges e)
        {
            if (Connected != null)
                Connected(this, e);
        }
        protected void OnError(EventChannelErrorArgs e)
        {
            if (Error != null)
            {
                try
                {
                    if (e.Error is SocketException || e.Error is NetTcpException || e.Error is ObjectDisposedException)
                    {

                        Dispose();
                        

                    }
                    Error(this, e);
                }
                catch
                {
                }
            }
        }
        protected virtual void OnCompleted(EventSendCompletedArgs e)
        {
            try
            {
                if (Completed != null)
                    Completed(this, e);
            }
            catch { }
        }
        protected virtual void OnReceive(EventChannelReceiveArgs e)
        {
            if (Receive != null)
                Receive(this, e);
        }
        protected void OnDisponse(EventChannelArges e)
        {
            lock (this)
            {
                if (mDisconnected)
                    return;
                mDisconnected = true;
                
                Close();
                if (Disposed != null)
                    Disposed(this, e);
            }
        }
        private void OnMessageReceive(IMessage message)
        {
             OnReceive(new EventChannelReceiveArgs { Channel = this, Message = Package.ReadCast( message) });
        }
        private System.Collections.Generic.Queue<IMessage> mSendMessages = new System.Collections.Generic.Queue<IMessage>();
        
        private bool mSendStatus = false;
        private void AddMessage(IMessage msg)
        {
            lock (mSendMessages)
            {
                mSendMessages.Enqueue(msg);
            }
        }
        private void OnSendMessage()
        {
            lock (mSendMessages)
            {
                if (!mSendStatus && mSendMessages.Count > 0)
                {
                    IMessage msg = mSendMessages.Dequeue();
                    byte[] data = msg.GetBuffer();//Package.GetMessageData(msg);
                    mSendStatus = true;
                    try
                    {
                        mSendEventArgs.UserToken = msg;
                        mSendEventArgs.SetBuffer(data, 0, data.Length);
                        if (!Socket.SendAsync(mSendEventArgs))
                        {
                            sendCompleted(this, mSendEventArgs);
                        }
                    }
                    catch (Exception e_)
                    {
                        mSendStatus = false;
                        CallError(e_);
                    }
                }
            }
        }
        public void Send(object message)
        {
            if (message == null)
                return;
            OnSend((IMessage)Package.WriteCast(message));
        }
        private void OnSend(IMessage message)
        {
           
                if (!Disconnected && !Socket.Connected)
                {
                    Dispose();
                    return;
                }
                AddMessage(message);
                OnSendMessage();

           
        }
        public void Connect(string host, int port)
        {
            Dispose();
            mSendStatus = false;
            SocketAsyncEventArgs saea = new SocketAsyncEventArgs();
            DnsEndPoint ep = new DnsEndPoint(host, port);
            saea.Completed += connectCompleted;
            saea.RemoteEndPoint = ep;
            mBeginConnectTime = DateTime.Now;

            mSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
           


            if (!mSocket.ConnectAsync( saea))
            {
                connectCompleted(this,saea);
            }

            
        }
        private void sendCompleted(object sender, SocketAsyncEventArgs e)
        {
            if (e.SocketError == System.Net.Sockets.SocketError.Success)
            {
               
                EventSendCompletedArgs sc = new EventSendCompletedArgs();
                sc.Channel = this;
                sc.Message = (IMessage)e.UserToken;
                //Request req = e.UserToken as Request;
                //if (req != null)
                //{
                //    dicWait.Add(req.Model.Seq, req);
                //}
                OnCompleted(sc);
                mSendStatus = false;

                OnSendMessage();
            }
            else
            {
                if (e.ConnectByNameError != null)
                {
                    mFailedInfo = e.ConnectByNameError.Message;
                    OnError(new EventChannelErrorArgs() { Error = e.ConnectByNameError, Channel = this });
                }
                Dispose();
            }
        }
        private void receiveCompleted(object sender, SocketAsyncEventArgs e)
        {
            
            if (e.SocketError == System.Net.Sockets.SocketError.Success)
            {
                try
                {
                    //IMessage m=e.UserToken as IMessage;
                     //Request m = e.UserToken as Request;
                     
                    //if(m!=null|| m.CompleteHandle!=null)
                    //{
                        Package.Import(e.Buffer, 0, e.BytesTransferred, null);
                    //}
                    BeginReceive();
                }
                catch (Exception e_)
                {
                    OnError(new EventChannelErrorArgs() { Error = e_, Channel = this });
                }
            }
            else
            {
               
                if (e.ConnectByNameError != null)
                {
                    mFailedInfo = e.ConnectByNameError.Message;
                    OnError(new EventChannelErrorArgs() { Error = e.ConnectByNameError, Channel = this });
                }
                Dispose();
            }
        }
        private void BeginReceive()
        {
            
            try
            {
                if (!Disconnected && !Socket.Connected)
                {
                    Dispose();
                    return;
                }
                if (!Socket.ReceiveAsync(mReceiveEventArgs))
                {
                    receiveCompleted(this, mReceiveEventArgs);
                }
            }
            catch (Exception e_)
            {
                CallError(e_);
            }
        }
        private void connectCompleted(object sender, SocketAsyncEventArgs e)
        {
            mSocketError = e.SocketError;
            if (mSocketError != System.Net.Sockets.SocketError.Success)
            {
                if (e.ConnectByNameError != null)
                {
                    mFailedInfo = e.ConnectByNameError.Message;
                    OnError(new EventChannelErrorArgs() { Error = e.ConnectByNameError, Channel = this });
                }
                else
                {
                    mFailedInfo = mSocketError.ToString();
                    OnError(new EventChannelErrorArgs() { Error = new Exception(mFailedInfo), Channel = this });
                }
            }
            else
            {
                this.Delay = (DateTime.Now - mBeginConnectTime).TotalMilliseconds;
                mDisconnected = false;
                mSendEventArgs = new SocketAsyncEventArgs();
                mReceiveEventArgs = new SocketAsyncEventArgs();
                mReceiveEventArgs.SetBuffer(new byte[receiveBufferSize], 0, receiveBufferSize);
                mReceiveEventArgs.Completed += receiveCompleted;
                mSendEventArgs.Completed += sendCompleted;
                BeginReceive();
                OnConnected(new EventChannelArges { Channel = this });
            }
        }
        public void Dispose()
        {
            OnDisponse(new EventChannelArges { Channel = this });
        }

    }
}
